package com.ib.algokit;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.apache.log4j.Logger;
/**
 * Holds the following associated variables:
 * <ul>
 * <li>candleT_MINUS_1YHistoryRequestState(AtomicInteger)</li>
 * <li>candleT_MINUS_5DHistoryRequestState(AtomicInteger)</li>
 * <li>specDetailsRequestState(AtomicInteger)</li>
 * <li>inHistoricFetch(AtomicBoolean)</li>
 * <li>inSpecDetailsFetch(AtomicBoolean)</li>
 * <li>liveCandleFeedActive(AtomicBoolean)</li>
 * <li>liveQuoteFeedActive(AtomicBoolean)</li>
 * <li>liveTradeIndicationFeedActive(AtomicBoolean)</li>
 * <li>liveMarketDepthFeedActive(AtomicBoolean)</li>
 * <li>activeMarketDepthFeeds(LinkedBlockingDeque&lt;InstrumentSpecification&gt;)</li>
 * </ul>
 * @author dmisev
 *
 */
public final class InstrumentAnalyzerState {
	/**
	 * protected final static Logger log = Logger.getLogger(InstrumentAnalyzerState.class);
	 */
	protected final static Logger log = Logger.getLogger(InstrumentAnalyzerState.class);
	/**
	 * private final EMA ema;
	 */
	//private final EMA ema;
	/**
	 * public static final int NOT_REQUESTED=0;
	 */
	public static final int NOT_REQUESTED=0;
	/**
	 * public static final int WAITING_TURN=1;
	 */
	public static final int WAITING_TURN=1;
	/**
	 * public static final int REQUEST_SENT=2;
	 */
	public static final int REQUEST_SENT=2;
	/**
	 * public static final int RESPONSE_RECEIVED_OK=3;
	 */
	public static final int RESPONSE_RECEIVED_OK=3;	
	/**
	 * public static final int RESPONSE_RECEIVED_NOT_OK=4;
	 */
	public static final int RESPONSE_RECEIVED_NOT_OK=4;
	/**
	 * public static final int LOADED_DATA_FROM_DATABASE=5;
	 */
	public static final int LOADED_DATA_FROM_DATABASE=5;
	/**
	 * private AtomicInteger candleT_MINUS_1YHistoryRequestState=new AtomicInteger(NOT_REQUESTED);
	 */
	private AtomicInteger candleT_MINUS_1YHistoryRequestState=new AtomicInteger(NOT_REQUESTED);	
	/**
	 * private AtomicInteger candleT_MINUS_5DHistoryRequestState=new AtomicInteger(NOT_REQUESTED);
	 */
	private AtomicInteger candleT_MINUS_5DHistoryRequestState=new AtomicInteger(NOT_REQUESTED);	
	/**
	 * private AtomicInteger tickerNewsT_MINUS_5DHistoryRequestState=new AtomicInteger(NOT_REQUESTED);
	 */
	private AtomicInteger tickerNewsT_MINUS_5DHistoryRequestState=new AtomicInteger(NOT_REQUESTED);	
	/**
	 * private AtomicInteger specDetailsRequestState=new AtomicInteger(NOT_REQUESTED);
	 */
	private AtomicInteger specDetailsRequestState=new AtomicInteger(NOT_REQUESTED);	
	/**
	 * private static AtomicBoolean inHistoricFetch = new AtomicBoolean(false);
	 */
	private static AtomicBoolean inHistoricFetch = new AtomicBoolean(false);
	/**
	 * private static AtomicBoolean inTickerNewsFetch = new AtomicBoolean(false);
	 */
	private static AtomicBoolean inTickerNewsFetch = new AtomicBoolean(false);
	/**
	 * private static AtomicBoolean inSpecDetailsFetch = new AtomicBoolean(false);
	 */
	private static AtomicBoolean inSpecDetailsFetch = new AtomicBoolean(false);
	/**
	 * private AtomicBoolean liveCandleFeedActive = new AtomicBoolean(false);
	 */
	private AtomicBoolean liveCandleFeedActive = new AtomicBoolean(false);
	/**
	 * private AtomicBoolean liveQuoteFeedActive = new AtomicBoolean(false);
	 */
	private AtomicBoolean liveQuoteFeedActive = new AtomicBoolean(false);
	/**
	 * private AtomicBoolean liveTradeIndicationFeedActive = new AtomicBoolean(false);
	 */
	private AtomicBoolean liveTradeIndicationFeedActive = new AtomicBoolean(false);
	/**
	 * private AtomicBoolean liveMarketDepthFeedActive = new AtomicBoolean(false);
	 */
	private AtomicBoolean liveMarketDepthFeedActive = new AtomicBoolean(false);
	/**
	 * private AtomicBoolean liveTickerNewsFeedActive = new AtomicBoolean(false);
	 */
	private AtomicBoolean liveTickerNewsFeedActive = new AtomicBoolean(false);
	/**
	 * private static final LinkedBlockingDeque&lt;InstrumentSpecification&gt; activeMarketDepthFeeds = new LinkedBlockingDeque&lt;InstrumentSpecification&gt;(3);
	 */
	private static final LinkedBlockingDeque<InstrumentSpecification> activeMarketDepthFeeds = new LinkedBlockingDeque<InstrumentSpecification>(3);
	/**
	 * returns the value of the associated liveCandleFeedActive(AtomicBoolean)
	 * @return
	 */
	public boolean isLiveCandleFeedActivated(){
		return this.liveCandleFeedActive.get();
	}
	/**
	 * returns the value of the associated liveQuoteFeedActive(AtomicBoolean)
	 * @return
	 */
	public boolean isLiveQuoteFeedActivated(){
		return this.liveQuoteFeedActive.get();
	}
	/**
	 * returns the value of the associated liveTradeIndicationFeedActive(AtomicBoolean)
	 * @return
	 */
	public boolean isLiveTradeIndicationFeedActivated(){
		return this.liveTradeIndicationFeedActive.get();
	}
	/**
	 * returns the value of the associated liveMarketDepthFeedActive(AtomicBoolean)
	 * @return
	 */
	public boolean isLiveMarketDepthFeedActivated(){
		return this.liveMarketDepthFeedActive.get();
	}
	/**
	 * returns the value of the associated liveTickerNewsFeedActive(AtomicBoolean)
	 * @return
	 */
	public boolean isLiveTickerNewsFeedActivated(){
		return this.liveTickerNewsFeedActive.get();
	}
	/**
	 * Sets the associated liveCandleFeedActive(AtomicBoolean) to true (its previous value should be false, otherwise it prints a warning)
	 */
	public void liveCandleFeedActivated(){
		if(this.liveCandleFeedActive.getAndSet(true)){
			log.warn("live Candle feed already active.");
			return;
		}
	}
	/**
	 * Sets the associated liveQuoteFeedActive(AtomicBoolean) to true (its previous value should be false, otherwise it prints a warning)
	 */
	public void liveQuoteFeedActivated(){
		if(this.liveQuoteFeedActive.getAndSet(true)){
			log.warn("live Quote feed already active.");
			return;
		}
	}
	/**
	 * Sets the associated liveTradeIndicationFeedActive(AtomicBoolean) to true (its previous value should be false, otherwise it prints a warning)
	 */
	public void liveTradeIndicationFeedActivated(){
		if(this.liveTradeIndicationFeedActive.getAndSet(true)){
			log.warn("live TradeIndication feed already active.");
			return;
		}
	}
	/**
	 * Since we can get up to 3 MarketDepth feeds at the same this cancels the oldest one to make room for the new one for the given spec(InstrumentSpecification)
	 * If we already have an active MarketDepth feed for the given spec(InstrumentSpecification), it returns without doing anything
	 * @param spec
	 */
	public void makeRoomForMarketDepthFeed(InstrumentSpecification spec){		
		if(InstrumentAnalyzerState.activeMarketDepthFeeds.contains(spec)){
			log.warn("live MarketDepth feed already among the 3 active MarketDepth feeds.");
			return;
		}
		synchronized(InstrumentAnalyzerState.activeMarketDepthFeeds){
			if(InstrumentAnalyzerState.activeMarketDepthFeeds.size()==3){
				InstrumentSpecification specToCancel=InstrumentAnalyzerState.activeMarketDepthFeeds.pollFirst();			
				InstrumentAnalyzer analyzer=InstrumentAnalyzer.getInstrumentAnalyzers().get(specToCancel);
				if(analyzer!=null)
				{analyzer.cancelLiveMarketDepthFeed();}
			}
		}	
		try {
			InstrumentAnalyzerState.activeMarketDepthFeeds.putLast(spec);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	/**
	 * Sets the associated liveMarketDepthFeedActive(AtomicBoolean) to true (its previous value should be false, otherwise it prints a warning)
	 */
	public void liveMarketDepthFeedActivated(){
		if(this.liveMarketDepthFeedActive.getAndSet(true)){
			log.warn("live MarketDepth feed already active.");
			return;
		}
	}
	/**
	 * Sets the associated liveTickerNewsFeedActive(AtomicBoolean) to true (its previous value should be false, otherwise it prints a warning)
	 */
	public void liveTickerNewsFeedActivated(){
		if(this.liveTickerNewsFeedActive.getAndSet(true)){
			log.warn("live TickerNews feed already active.");
			return;
		}
	}
	/**
	 * Sets the associated liveCandleFeedActive(AtomicBoolean) to false (its previous value should be true, otherwise it prints a warning)
	 */
	public void liveCandleFeedDeactivated(){
		if(!this.liveCandleFeedActive.getAndSet(false)){
			log.warn("live Candle feed already deactivated.");
			return;
		}
	}
	/**
	 * Sets the associated liveQuoteFeedActive(AtomicBoolean) to false (its previous value should be true, otherwise it prints a warning)
	 */
	public void liveQuoteFeedDeactivated(){
		if(!this.liveQuoteFeedActive.getAndSet(false)){
			log.warn("live Quote feed already deactivated.");
			return;
		}
	}
	/**
	 * Sets the associated liveTradeIndicationFeedActive(AtomicBoolean) to false (its previous value should be true, otherwise it prints a warning)
	 */
	public void liveTradeIndicationFeedDeactivated(){
		if(!this.liveTradeIndicationFeedActive.getAndSet(false)){
			log.warn("live TradeIndication feed already deactivated.");
			return;
		}
	}
	/**
	 * Sets the associated liveMarketDepthFeedActive(AtomicBoolean) to false (its previous value should be true, otherwise it prints a warning)
	 */
	public void liveMarketDepthFeedDeactivated(){
		if(!this.liveMarketDepthFeedActive.getAndSet(false)){
			log.warn("live MarketDepth feed already deactivated.");
			return;
		}
	}
	/**
	 * Sets the associated liveTickerNewsFeedActive(AtomicBoolean) to false (its previous value should be true, otherwise it prints a warning)
	 */
	public void liveTickerNewsFeedDeactivated(){
		if(!this.liveTickerNewsFeedActive.getAndSet(false)){
			log.warn("live TickerNews feed already deactivated.");
			return;
		}
	}
	/**
	 * returns whether the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) is set to NOT_REQUESTED(0)
	 * @return
	 */
	public boolean isCandleT_MINUS_1YHistoryNotRequested() {
		return candleT_MINUS_1YHistoryRequestState.get()==NOT_REQUESTED;
	}
	/**
	 * returns whether the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) is set to WAITING_TURN(1)
	 * @return
	 */
	public boolean isCandleT_MINUS_1YHistoryRequestWaitingTurn() {
		return candleT_MINUS_1YHistoryRequestState.get()==WAITING_TURN;
	}
	/**
	 * returns whether the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) is set to REQUEST_SENT(2), RESPONSE_RECEIVED_OK(3) or RESPONSE_RECEIVED_NOT_OK(4)
	 * @return
	 */
	public boolean isCandleT_MINUS_1YHistoryRequestSent() {
		return candleT_MINUS_1YHistoryRequestState.get()>=REQUEST_SENT;
	}
	/**
	 * First it sets the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) to WAITING_TURN(1), then waits for its turn and then sets
	 * the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) to REQUEST_SENT(2)
	 */
	public void standInLineForCandleT_MINUS_1YHistoryRequest() {
		if(this.candleT_MINUS_1YHistoryRequestState.incrementAndGet()!=WAITING_TURN){
			log.warn("candleT_MINUS_1YHistoryRequestState should be in WAITING_TURN state.");
			return;
		}
		while(inHistoricFetch.getAndSet(true)){
			synchronized (inHistoricFetch) {
				try {
					inHistoricFetch.wait();
				} catch (InterruptedException ex) {
					log.info("I woke up and inHistoricFetch=" + inHistoricFetch.get());
				}
			}
		}
		if(this.candleT_MINUS_1YHistoryRequestState.incrementAndGet()!=REQUEST_SENT){
			log.warn("candleT_MINUS_1YHistoryRequestState should be in REQUEST_SENT state.");
		}
	}	
	/**
	 * First it sets the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) to WAITING_TURN(1), then waits for its turn and then sets
	 * the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) to REQUEST_SENT(2)
	 */
	public void standInLineForCandleT_MINUS_5DHistoryRequest() {
		if(this.candleT_MINUS_5DHistoryRequestState.incrementAndGet()!=WAITING_TURN){
			log.warn("candleT_MINUS_5DHistoryRequestState should be in WAITING_TURN state.");
			return;
		}
		while(inHistoricFetch.getAndSet(true)){
			synchronized (inHistoricFetch) {
				try {
					inHistoricFetch.wait();
				} catch (InterruptedException ex) {
					log.info("I woke up and inHistoricFetch=" + inHistoricFetch.get());
				}
			}
		}
		if(this.candleT_MINUS_5DHistoryRequestState.incrementAndGet()!=REQUEST_SENT){
			log.warn("candleT_MINUS_5DHistoryRequestState should be in REQUEST_SENT state.");
		}
	}
	public void standInLineForTickerNewsT_MINUS_5DHistoryRequest() {
		if(this.tickerNewsT_MINUS_5DHistoryRequestState.incrementAndGet()!=WAITING_TURN){
			log.warn("tickerNewsT_MINUS_5DHistoryRequestState should be in WAITING_TURN state.");
			return;
		}
		while(inTickerNewsFetch.getAndSet(true)){
			synchronized (inTickerNewsFetch) {
				try {
					inTickerNewsFetch.wait();
				} catch (InterruptedException ex) {
					log.info("I woke up and inTickerNewsFetch=" + inTickerNewsFetch.get());
				}
			}
		}
		if(this.tickerNewsT_MINUS_5DHistoryRequestState.incrementAndGet()!=REQUEST_SENT){
			log.warn("tickerNewsT_MINUS_5DHistoryRequestState should be in REQUEST_SENT state.");
		}
	}
	/**
	 * First it sets the associated specDetailsRequestState(AtomicInteger) to WAITING_TURN(1), then waits for its turn and then sets
	 * the associated specDetailsRequestState(AtomicInteger) to REQUEST_SENT(2)
	 */
	public void standInLineForSpecDetailsRequest() {
		if(this.specDetailsRequestState.incrementAndGet()!=WAITING_TURN){
			log.warn("specDetailsRequestState should be in WAITING_TURN state.");
			return;
		}
		while(inSpecDetailsFetch.getAndSet(true)){
			synchronized (inSpecDetailsFetch) {
				try {
					inSpecDetailsFetch.wait();
				} catch (InterruptedException ex) {
					log.info("I woke up and inSpecDetailsFetch=" + inSpecDetailsFetch.get());
				}
			}
		}
		if(this.specDetailsRequestState.incrementAndGet()!=REQUEST_SENT){
			log.warn("specDetailsRequestState should be in REQUEST_SENT state.");
		}
	}
	/**
	 * It waits until the associated inSpecDetailsFetch(AtomicBoolean) is true, but no longer than 5 secs
	 */
	public void waitSpecDetailsFinish() {
//		if(this.specDetailsRequestState.get()!=REQUEST_SENT){
//			log.warn("specDetailsRequestState should be in REQUEST_SENT state.");
//			return;
//		}
		while(inSpecDetailsFetch.get()){
			synchronized (inSpecDetailsFetch) {
				try {
					inSpecDetailsFetch.wait(5000);
				} catch (InterruptedException ex) {
					log.info("I woke up and inSpecDetailsFetch=" + inSpecDetailsFetch.get());
				}
			}
		}
	}
	/**
	 * returns whether the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) is set to RESPONSE_RECEIVED_OK(3) or RESPONSE_RECEIVED_NOT_OK(4)
	 * @return
	 */
	public boolean isCandleT_MINUS_1YHistoryRequestReceived() {
		return candleT_MINUS_1YHistoryRequestState.get()>=RESPONSE_RECEIVED_OK;
	}
	
	/**
	 * returns whether the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) is set to RESPONSE_RECEIVED_OK(3) or LOADED_DATA_FROM_DATABASE(5)
	 * @return
	 */
	public boolean isCandleT_MINUS_1YHistoryRequestReceivedOK() {
		return candleT_MINUS_1YHistoryRequestState.get()==RESPONSE_RECEIVED_OK || candleT_MINUS_1YHistoryRequestState.get()==LOADED_DATA_FROM_DATABASE;
	}
	
	/**
	 * returns whether the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) is set to RESPONSE_RECEIVED_OK(3) or LOADED_DATA_FROM_DATABASE(5)
	 * @return
	 */
	public boolean isCandleT_MINUS_5DHistoryRequestReceivedOK() {
		return candleT_MINUS_5DHistoryRequestState.get()==RESPONSE_RECEIVED_OK || candleT_MINUS_5DHistoryRequestState.get()==LOADED_DATA_FROM_DATABASE;
	}
	/**
	 * returns whether the associated tickerNewsT_MINUS_5DHistoryRequestState(AtomicInteger) is set to RESPONSE_RECEIVED_OK(3) or LOADED_DATA_FROM_DATABASE(5)
	 * @return
	 */
	public boolean isTickerNewsT_MINUS_5DHistoryRequestReceivedOK() {
		return tickerNewsT_MINUS_5DHistoryRequestState.get()==RESPONSE_RECEIVED_OK || tickerNewsT_MINUS_5DHistoryRequestState.get()==LOADED_DATA_FROM_DATABASE;
	}
	/**
	 * Sets the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) to LOADED_DATA_FROM_DATABASE(5)
	 */
	public void setCandleT_MINUS_1YHistoryLoadedFromDatabase() {
		this.candleT_MINUS_1YHistoryRequestState.set(LOADED_DATA_FROM_DATABASE);
	}
	/**
	 * Sets the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) to LOADED_DATA_FROM_DATABASE(5)
	 */
	public void setCandleT_MINUS_5DHistoryLoadedFromDatabase() {
		this.candleT_MINUS_5DHistoryRequestState.set(LOADED_DATA_FROM_DATABASE);
	}
	/**
	 * Sets the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) to LOADED_DATA_FROM_DATABASE(5)
	 */
	public void setTickerNewsT_MINUS_5DHistoryLoadedFromDatabase() {
		this.tickerNewsT_MINUS_5DHistoryRequestState.set(LOADED_DATA_FROM_DATABASE);
	}
	/**
	 * Sets the associated candleT_MINUS_1YHistoryRequestState(AtomicInteger) to RESPONSE_RECEIVED_OK(3) or RESPONSE_RECEIVED_NOT_OK(4) depending on the given responseOK(boolean)
	 * and notifies the others that the historic fetch is now available
	 * @param responseOK
	 */
	public void setCandleT_MINUS_1YHistoryRequestReceived(boolean responseOK) {
		if (this.candleT_MINUS_1YHistoryRequestState.incrementAndGet() != RESPONSE_RECEIVED_OK) {
			log.warn("candleT_MINUS_1YHistoryRequestState should be in RESPONSE_RECEIVED_OK state.");
		}
		if (!responseOK) {
			if (this.candleT_MINUS_1YHistoryRequestState.incrementAndGet() != RESPONSE_RECEIVED_NOT_OK) {
				log.warn("candleT_MINUS_1YHistoryRequestState should be in RESPONSE_RECEIVED_NOT_OK state.");
			}
		}
		inHistoricFetch.set(false);
		synchronized (inHistoricFetch) {
			inHistoricFetch.notifyAll();
		}
	}
	/**
	 * Sets the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) to RESPONSE_RECEIVED_OK(3) or RESPONSE_RECEIVED_NOT_OK(4) depending on the given responseOK(boolean)
	 * and notifies the others that the historic fetch is now available
	 * @param responseOK
	 */
	public void setCandleT_MINUS_5DHistoryRequestReceived(boolean responseOK) {
		if (this.candleT_MINUS_5DHistoryRequestState.incrementAndGet() != RESPONSE_RECEIVED_OK) {
			log.warn("candleT_MINUS_5DHistoryRequestState should be in RESPONSE_RECEIVED_OK state.");
		}
		if (!responseOK) {
			if (this.candleT_MINUS_5DHistoryRequestState.incrementAndGet() != RESPONSE_RECEIVED_NOT_OK) {
				log.warn("candleT_MINUS_5DHistoryRequestState should be in RESPONSE_RECEIVED_NOT_OK state.");
			}
		}
		inHistoricFetch.set(false);
		synchronized (inHistoricFetch) {
			inHistoricFetch.notifyAll();
		}
	}
	/**
	 * Sets the associated candleT_MINUS_5DHistoryRequestState(AtomicInteger) to RESPONSE_RECEIVED_OK(3) or RESPONSE_RECEIVED_NOT_OK(4) depending on the given responseOK(boolean)
	 * and notifies the others that the historic fetch is now available
	 * @param responseOK
	 */
	public void setTickerNewsT_MINUS_5DHistoryRequestReceived(boolean responseOK) {
		if (this.tickerNewsT_MINUS_5DHistoryRequestState.incrementAndGet() != RESPONSE_RECEIVED_OK) {
			log.warn("tickerNewsT_MINUS_5DHistoryRequestState should be in RESPONSE_RECEIVED_OK state.");
		}
		if (!responseOK) {
			if (this.tickerNewsT_MINUS_5DHistoryRequestState.incrementAndGet() != RESPONSE_RECEIVED_NOT_OK) {
				log.warn("tickerNewsT_MINUS_5DHistoryRequestState should be in RESPONSE_RECEIVED_NOT_OK state.");
			}
		}
		inTickerNewsFetch.set(false);
		synchronized (inTickerNewsFetch) {
			inTickerNewsFetch.notifyAll();
		}

	}
	/**
	 * Sets the associated specDetailsRequestState(AtomicInteger) to RESPONSE_RECEIVED_OK(3) or RESPONSE_RECEIVED_NOT_OK(4) depending on the given responseOK(boolean)
	 * and notifies the others that the contract details fetch is now available
	 * @param responseOK
	 */
	public void setSpecDetailsRequestReceived(boolean responseOK) {
		if (this.specDetailsRequestState.incrementAndGet() != RESPONSE_RECEIVED_OK) {
			log.warn("specDetailsRequestState should be in RESPONSE_RECEIVED_OK state.");
		}
		if (!responseOK) {
			if (this.specDetailsRequestState.incrementAndGet() != RESPONSE_RECEIVED_NOT_OK) {
				log.warn("specDetailsRequestState should be in RESPONSE_RECEIVED_NOT_OK state.");
			}
		}
		inSpecDetailsFetch.set(false);
		synchronized (inSpecDetailsFetch) {
			inSpecDetailsFetch.notifyAll();
		}
	}
	
	public void resetSpecDetailsRequestState(){
		this.specDetailsRequestState.set(NOT_REQUESTED);
	}
	
	public InstrumentAnalyzerState() {
		//this.ema = ema;
	}
	
}
